using System.Linq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class ProductionParserErrorRecoveryFeature {
        private NonTerminal Production;
        private IProduction AlphaIdentifierProduction;

        [Test]
        public void ShouldThrowExceptionIfNotParsed () {
            SetupAlphaBeta ();

            var parser = new ProductionParser (Production);

            try {
                parser.Parse ("8, b", "filename");
                Assert.Fail ("expected: " + typeof (SyntaxException));
            } catch (SyntaxException se) {
                Assert.That (se.SyntaxErrors.Count (), Is.EqualTo (1));

                Assert.That (se.Term, Is.Not.Null);

                var term = (CompositeTerm) se.Term;
                Assert.That (term.Name, Is.EqualTo ("alphabet"));
                var alphaTerm = (ErrorTerm) term.SubTerms ["alpha"];
                Assert.That (alphaTerm.Production, Is.SameAs (AlphaIdentifierProduction));
                Assert.That (alphaTerm.SourceInformation.Index, Is.EqualTo (0));
                Assert.That (alphaTerm.SourceInformation.Length, Is.EqualTo (1));
                var betaTerm = (IdentifierTerm) term.SubTerms ["beta"];
                Assert.That (betaTerm.Name, Is.EqualTo ("b"));
            }
        }

        private void SetupAlphaBeta () {
            var delimiter = new KeywordRule (",");
            var alphaIdentifierTerminal = IdentifierProduction.CreateTerminal ();
            var alphaProduction = new RecoveryProduction (alphaIdentifierTerminal, delimiter.Production);
            var alpha = new NamedRule ("alpha", alphaProduction);
            var beta = new NamedRule ("beta", IdentifierProduction.CreateTerminal ());
            AlphaIdentifierProduction = alphaIdentifierTerminal.TerminalProduction;
            Production = new NonTerminal ("alphabet", new SequenceRule (alpha, delimiter, beta));
        }

        [Test]
        public void ShouldThrowExceptionIfNotParsedWithAllErrorTerms () {
            SetupAlphaBetaGamma ();

            var parser = new ProductionParser (Production);

            try {
                parser.Parse ("8, 7, b", "filename");
                Assert.Fail ("expected: " + typeof (SyntaxException));
            } catch (SyntaxException se) {
                Assert.That (se.SyntaxErrors.Count (), Is.EqualTo (2));

                Assert.That (se.Term, Is.Not.Null);

                var term = (CompositeTerm) se.Term;
                Assert.That (term.Name, Is.EqualTo ("alphabet"));
                var alphaTerm = (ErrorTerm) term.SubTerms ["alpha"];
                Assert.That (alphaTerm.SourceInformation, Is.EqualTo (new SourceInformation ("filename", 0, 1)));
                var betaTerm = (ErrorTerm) term.SubTerms ["beta"];
                Assert.That (betaTerm.SourceInformation, Is.EqualTo (new SourceInformation ("filename", 3, 1)));
                var gammaTerm = (IdentifierTerm) term.SubTerms ["gamma"];
                Assert.That (gammaTerm.Name, Is.EqualTo ("b"));
            }
        }

        [Test]
        public void ShouldThrowExceptionIfNoRecoveryWasPossible () {
            SetupAlphaBetaGamma ();

            var parser = new ProductionParser (Production);

            try {
                parser.Parse ("8; 7; b", "filename");
                Assert.Fail ("expected: " + typeof (SyntaxException));
            } catch (SyntaxException se) {
                Assert.That (se.SyntaxErrors.Count (), Is.EqualTo (1));

                Assert.That (se.Term, Is.Not.Null);

                var term = (ErrorTerm) se.Term;
                Assert.That (term.SourceInformation, Is.EqualTo (new SourceInformation ("filename", 0, 7)));
            }
        }

        private void SetupAlphaBetaGamma () {
            var delimiter = new KeywordRule (",");
            var rescuableIdentifier = new RecoveryProduction (IdentifierProduction.CreateTerminal (), delimiter.Production);
            var alpha = new NamedRule ("alpha", rescuableIdentifier);
            var beta = new NamedRule ("beta", rescuableIdentifier);
            var gamma = new NamedRule ("gamma", IdentifierProduction.CreateTerminal ());
            Production = new NonTerminal ("alphabet", new SequenceRule (alpha, delimiter, beta, delimiter, gamma));
        }
    }
}